# -*- coding: utf-8 -*-

import exceptions
import django.db.models as models
from functools import partial

def alias(converted_name, source_name):
    result = (converted_name, lambda x : getattr(x, source_name))
    return result

class _AttributeGetter:
    def __init__(self, attribute_name):
        self.attribute_name = attribute_name

    def __call__(self, argument):
        result = getattr(argument, self.attribute_name)
        if isinstance(result, models.Manager):    # ManyToMany fields are represented by a manager, not directly data
            result = result.all()
        return result


class Converter:
    class ContextFormatError(exceptions.Exception):
        def __init__(self, message):
            self.message = message

        def __str__(self):
            return 'Context format error: ' % self.message


    class MultipleResolutionError(exceptions.Exception):
        def __init__(self, model_name, name_list):
            self.model_name = model_name
            self.name_list = name_list

        def __str__():
            return "Tried promotion to subclasses for %s, multiple possibilities: %s" % (self.model_name, ', '.join(self.name_list))


    class SubmodelsFinder:
        """
        Finds all submodels of given model whose names are listed in 'possible_names'
        May not work correctly if there are models with same name in different modules
        """
        def __init__(self, data, possible_names):
            self.data = data
            self.possible_names = possible_names

            self.found_candidates = []
            self.candidate_names = []

        def __call__(self):
            self.run(self.data)
            return self.found_candidates

        def run(self, data):
            new_candidate_names = filter(lambda x: hasattr(data, x.lower()), self.possible_names)
            new_candidate_names = filter(lambda x: x not in self.candidate_names, new_candidate_names)

            self.candidate_names.extend(new_candidate_names)

            for name in new_candidate_names:
                new_data = getattr(data, name.lower())
                self.found_candidates.append(new_data)
                self.run(new_data)


    def __init__(self, context = {}):
        self.context = {}
        for key in context:
            if not isinstance(context[key], list):
                raise self.ContextFormatError('Fields should come in list')
            fields = {}
            for context_item in context[key]:
                if isinstance(context_item, basestring):
                    fields[context_item] = _AttributeGetter(context_item)
                else: #Expect a (name, action)
                    fields[context_item[0]] = context_item[1]
            self.context[key] = fields

    def convert(self, data):
        if isinstance(data, dict):
            return self.convertDict(data)
        if getattr(data, '__iter__', None):
            return self.convertIterable(data)
        elif isinstance(data, models.Model):
            return self.convertModel(data)
        elif isinstance(data, (int, long, float)):
            return data
        else:
            return unicode(data)

    def convertIterable(self, data):
        return map(self.convert, data)

    def convertDict(self, data):
        result = {}
        for item in data.iteritems():
            result[item[0]] = self.convert(item[1])
        return result

    def convertModel(self, data):
        result = {}
        model_name = data._meta.object_name
        try:
            model_context = self.context[model_name]
        except KeyError:
            # May be we have a rule for a subclass of the model
            candidates = Converter.SubmodelsFinder(data, self.context.keys())()
            if len(candidates) == 0:
                raise
            if len(candidates) > 1:
                raise Converter.MultipleResolutionError(model_name, map(lambda x: x.name, candidates))
            # Cast to the necessary type and do processing
            return self.convertModel(candidates[0])

        for name in model_context:
            result[name] = self.convert(model_context[name](data))
        return result


def convert(data, context):
    converter = Converter(context)
    return converter.convert(data)
